"""Contains time pickers exposed by the Starlink integration."""

from __future__ import annotations

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from datetime import UTC, datetime, time, tzinfo
import math

from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .coordinator import StarlinkConfigEntry, StarlinkData, StarlinkUpdateCoordinator
from .entity import StarlinkEntity


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: StarlinkConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up all time entities for this entry."""
    async_add_entities(
        StarlinkTimeEntity(config_entry.runtime_data, description)
        for description in TIMES
    )


@dataclass(frozen=True, kw_only=True)
class StarlinkTimeEntityDescription(TimeEntityDescription):
    """Describes a Starlink time entity."""

    value_fn: Callable[[StarlinkData, tzinfo], time | None]
    update_fn: Callable[[StarlinkUpdateCoordinator, time], Awaitable[None]]
    available_fn: Callable[[StarlinkData], bool]


class StarlinkTimeEntity(StarlinkEntity, TimeEntity):
    """A TimeEntity for Starlink devices. Handles creating unique IDs."""

    entity_description: StarlinkTimeEntityDescription

    @property
    def native_value(self) -> time | None:
        """Return the value reported by the time."""
        return self.entity_description.value_fn(
            self.coordinator.data, self.coordinator.timezone
        )

    @property
    def available(self) -> bool:
        """Return True if entity is available."""
        return self.entity_description.available_fn(self.coordinator.data)

    async def async_set_value(self, value: time) -> None:
        """Change the time."""
        return await self.entity_description.update_fn(self.coordinator, value)


def _utc_minutes_to_time(utc_minutes: int, timezone: tzinfo) -> time:
    hour = math.floor(utc_minutes / 60)
    if hour > 23:
        hour -= 24
    minute = utc_minutes % 60
    try:
        utc = datetime.now(UTC).replace(
            hour=hour, minute=minute, second=0, microsecond=0
        )
    except ValueError as exc:
        raise HomeAssistantError from exc
    return utc.astimezone(timezone).time()


def _time_to_utc_minutes(t: time, timezone: tzinfo) -> int:
    try:
        zoned_time = datetime.now(timezone).replace(
            hour=t.hour, minute=t.minute, second=0, microsecond=0
        )
    except ValueError as exc:
        raise HomeAssistantError from exc
    utc_time = zoned_time.astimezone(UTC).time()
    return (utc_time.hour * 60) + utc_time.minute


TIMES = [
    StarlinkTimeEntityDescription(
        key="sleep_start",
        translation_key="sleep_start",
        value_fn=lambda data, timezone: _utc_minutes_to_time(data.sleep[0], timezone),
        update_fn=lambda coordinator, time: coordinator.async_set_sleep_start(
            _time_to_utc_minutes(time, coordinator.timezone)
        ),
        available_fn=lambda data: data.sleep[2],
    ),
    StarlinkTimeEntityDescription(
        key="sleep_end",
        translation_key="sleep_end",
        value_fn=lambda data, timezone: _utc_minutes_to_time(
            data.sleep[0] + data.sleep[1], timezone
        ),
        update_fn=lambda coordinator, time: coordinator.async_set_sleep_duration(
            _time_to_utc_minutes(time, coordinator.timezone)
        ),
        available_fn=lambda data: data.sleep[2],
    ),
]
